• 问题

    Java 1.5之前,一般使用命名模式表明有些程序元素需要通过某种工具或者框架进行特殊处理。例如,在老版本的JUnit测试框架原本要求用户一定要用test作为测试方法名称的开头,这种方式就是命名模式。

    命名模式的缺点:

    • 文字拼写错误导致失败,测试方法没有执行,也没有报错。例如,假设意外地命名了测试方法tsetSafetyOverride而不是testSafetyOverride。 JUnit 3不会报错,但它也不会执行测试,导致错误的安全感;
    • 无法确保它们只用于相应的程序元素上, 例如,假设调用了TestSafetyMechanisms类,希望JUnit 3能够自动测试其所有方法,而不管它们的名称如何。 同样,JUnit 3也不会出错,但它也不会执行测试;
    • 没有提供将参数值与程序元素关联起来的好方法。

    针对命名模式带来的这些问题,该如何解决?

  • 解决

    注解很好地解决了由命名模式带来的所有这些问题,实际上Junit4也开始采用注解的方式代替了以往的命名模式。

    1. 如何定义注解

      例如定义一个Test的注解:

      @Retention(RetentionPolicy.RUNTIME)
      
      @Target(ElementType.METHOD)
      
      public @interface Test {
      
      }
      

      Test注解类型的声明本身使用RetentionTarget注解进行标记。 注解类型声明上的这种注解称为元注解@Retention(RetentionPolicy.RUNTIME)元注解指示Test注解应该在运行时保留。 Retention的取值有这些:

      1. RetentionPolicy.SOURCE —— 这种类型的Annotations只在源代码级别保留,编译时就会被忽略
      2. RetentionPolicy.CLASS—— 这种类型的Annotations编译时被保留,在class文件中存在,但JVM将会忽略
      3. RetentionPolicy.RUNTIME —— 这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用.

      @Target.get(ElementType.METHOD)元注解表明Test注解只对方法声明合法:它不能应用于类声明,属性声明或其他程序元素。Target的取值有这些:

      1. CONSTRUCTOR:用于描述构造器
      2. FIELD:用于描述域
      3. LOCAL_VARIABLE:用于描述局部变量
      4. METHOD:用于描述方法
      5. PACKAGE:用于描述包
      6. PARAMETER:用于描述参数
      7. TYPE:用于描述类、接口(包括注解类型) 或enum声明
    2. 使用注解

       @Test public static void m1() { }
      

      以下是Test注解在实践中的应用。 它被称为标记注解,因为它没有参数,只是“标记”注解元素。 如果程序员错拼Test或将Test注解应用于程序元素而不是方法声明,则该程序将无法编译

    3. 利用反射和注解结合发挥语义上的作用

      Test注解对标注的注解元素的语义没有直接影响。 他们只提供信息供相关程序使用。 更一般地说,注解不会改变注解代码的语义,但是可以将反射和注解结合起来,发挥更强大的作用。

      public class RunTests {
          public static void main(String[] args) throws Exception {
              int tests = 0;
              int passed = 0;
              Class<?> testClass = Class.forName(args[0]);
              for (Method m : testClass.getDeclaredMethods()) {
                  if (m.isAnnotationPresent(Test.class)) {
                      tests++;
                      try {
                          m.invoke(null);
                          passed++;
                      } catch (InvocationTargetException wrappedExc) {
                          Throwable exc = wrappedExc.getCause();
                          System.out.println(m + " failed: " + exc);
                      } catch (Exception exc) {
                          System.out.println("Invalid @Test: " + m);
                      }
                  }
              }
              System.out.printf("Passed: %d, Failed: %d%n",
                                passed, tests - passed);
          }
      }
      

      上例代码通过反射,让使用了@Test注解的方法能够执行,并且统计了被@Test标注的方法的个数以及方法成功执行的个数。可以看出,只有让注解和反射结合起来使用时,才能发挥更强大的语义作用。

  • 总结

    当可以使用注解代替时,没有理由使用命名模式。实际上Java已经提供了很多预定义的注解类型,在实际项目中应该尽可能多使用注解。另外,请考虑使用IDE或静态分析工具提供的注解。 这些注解可以提高这些工具提供的诊断信息的质量。

results matching ""

    No results matching ""